﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Data.Schema.Package.Project;
using System.IO;
using TFDP.Common;
using System.Diagnostics;
using TFDP.VsPackage.Services;
using TFDP.VisualStudioInterop;

namespace TFDP.VsPackage.Support.TargetProjects
{
    internal sealed class DatabaseProject : ITargetProject
    {
        private IVsHierarchy projectHierarchy;
        private IDatabaseProjectNode project;
        private IVsQueryEditQuerySave2 queryEditService;
        internal IFileChangeService FileChangeService { get; set; }

        public bool Update(Common.GeneratedArtifact artifact)
        {
            bool addToProject = (artifact.ModelElement == null);
            string targetFile = artifact.TargetFileName;
            bool overwriteAlways = artifact.Properties.ContainsKey(CommonProperties.OverwriteAlways) && artifact.Properties[CommonProperties.OverwriteAlways] == Boolean.TrueString;

            IDatabaseFileNode node = NodeFromPath(targetFile);

            if (!Path.IsPathRooted(targetFile))
                targetFile = node.Url;

            if (addToProject && node != null)
            {
                addToProject = false;
            }

            if (!addToProject)
            {
                // Compare files to determine changes
                if (!overwriteAlways && ProjectManager.FilesAreEqual(artifact.TemporaryFileName, targetFile))
                    return true;

                // Files are not equal, request edit
                FileEditRequestResult result = queryEditService.CanEdit(artifact.TargetFileName);
                if (result != null && !result.Success)
                {
                    Logger.LogExtended(TraceLevel.Error, String.Format("Unable to write file: {0}", result.Message), targetFile, 1, 1);
                    return false;
                }

                FileChangeService.BeforeFileChange(targetFile);
            }

            try
            {
                // Copy the temporaryfile over the targetfile
                File.Copy(artifact.TemporaryFileName, targetFile, true);
            }
            finally
            {
                if(!addToProject)
                    FileChangeService.AfterFileChange(targetFile);
            }

            if(addToProject)
            {
                if (!project.QueryEditProjectFile(true))
                {
                    Logger.LogExtended(TraceLevel.Error, "The project is not editable at this moment", targetFile, 1, 1);
                    return false;
                }

                FileInfo info = new FileInfo(targetFile);
                IDatabaseFileNode newNode = null;

                bool addToParent = (artifact.Properties.ContainsKey(CommonProperties.AddToParent) && artifact.Properties[CommonProperties.AddToParent] == Boolean.TrueString);
                string parentFile = artifact.Properties.ContainsKey(CommonProperties.Parent) ? artifact.Properties[CommonProperties.Parent] : String.Empty;

                IDatabaseNode parentNode = null;

                if (addToParent && !String.IsNullOrEmpty(parentFile))
                {
                    parentNode = project.FindNode(parentFile);
                }
                else
                {
                    parentNode = project.FindNode(info.DirectoryName);
                }

                project.CreateFileNode(
                    parentNode,
                    targetFile,
                    out newNode
                );
                
                newNode.BuildAction = "Build";

                foreach (var kv in artifact.Properties)
                {
                    newNode.SetItemProperty(kv.Key, kv.Value);
                }

                try
                {
                    project.IncludeInProject(new IDatabaseNode[] { newNode });
                }
                catch (Exception ex)
                {
                    Logger.LogError(ex.ToString());
                    return false;
                }

                bool isGenerated = true;

                if (artifact.Properties.ContainsKey(CommonProperties.IsGeneratedProperty))
                {
                    Boolean.TryParse(artifact.Properties[CommonProperties.IsGeneratedProperty], out isGenerated);
                }

                // Set IsGenerated to true
                newNode.SetItemProperty(CommonProperties.IsGeneratedProperty, isGenerated ? Boolean.TrueString : Boolean.FalseString);
            }

            return true;
        }

        public void Initialize(IVsHierarchy projectHierarchy)
        {
            this.projectHierarchy = projectHierarchy;
            IVsHierarchy hier = (IVsHierarchy)VsInteropUtilities.GetEnvDTEProject(projectHierarchy).Object;
            this.project = VsInteropUtilities.GetDatabaseProject(hier);
            
            // Obtain QueryEdit/QuerySave service
            this.queryEditService = (IVsQueryEditQuerySave2)this.project.GetService(typeof(SVsQueryEditQuerySave));
            this.FileChangeService = (IFileChangeService)this.project.GetService(typeof(IFileChangeService));
        }

        /// <summary>
        /// Obtain the IDatabaseFileNode for the given path
        /// </summary>
        /// <param name="path">The path to locate</param>
        /// <returns>a IDatabaseFileNode if found, otherwise null</returns>
        private IDatabaseFileNode NodeFromPath(string path)
        {
            if (String.IsNullOrEmpty(path))
                throw new ArgumentNullException("path");

            try
            {
                return (IDatabaseFileNode)project.FindNode(path);
            }
            catch (Exception)
            {
                return null;
            }
        }
    }
}
